#include "Common.h"
#include "TcpServer.h"
#include "TcpClient.h"
#include "NetworkManager.h"
#include <algorithm>

using namespace ENet;
static ENetAddress pongaddr;

TcpServer::TcpServer()
	: mServerPort(DEFAULT_PORT)
	, mServerHost(NULL)
	, mPongSock(ENET_SOCKET_NULL)
	, mLanSock(ENET_SOCKET_NULL)
{
}

TcpServer::~TcpServer()
{
	TcpServer::CleanUp();
}

void TcpServer::CleanUp()
{
	if(mServerHost) 
		enet_host_destroy(mServerHost);
	mServerHost = NULL;

	if (mPongSock != ENET_SOCKET_NULL)
		enet_socket_destroy(mPongSock);
	if (mLanSock != ENET_SOCKET_NULL) 
		enet_socket_destroy(mLanSock);
	mPongSock = mLanSock = ENET_SOCKET_NULL;
}

bool TcpServer::Listen(WORD port, int maxClients)
{
	mServerPort = port;

	// create the enet host.
	ENetAddress address = { ENET_HOST_ANY, getServerPort() };
	mServerHost = enet_host_create(&address, Min(maxClients, (int)MAXCLIENTS), NUMCHANNELS, 0, 0);
	if (mServerHost == NULL)
		return false;

	// clean the peers.
	loopi(maxClients) mServerHost->peers[i].data = NULL;

	// create and set the pong socket (server info).
	ENetAddress serverInfoAddress = { ENET_HOST_ANY, getServerInfoPort() };
    mPongSock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
    if (mPongSock == ENET_SOCKET_NULL || enet_socket_bind(mPongSock, &serverInfoAddress) < 0)
    {
        if (mPongSock != ENET_SOCKET_NULL)
			enet_socket_destroy(mPongSock);
        mPongSock = ENET_SOCKET_NULL;
		return false;
    }
	enet_socket_set_option(mPongSock, ENET_SOCKOPT_NONBLOCK, 1);

	// create and setup the lan socket (lan info).
	ENetAddress lanInfoAddress = { ENET_HOST_ANY, getLanInfoPort() };
    mLanSock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
    if(mLanSock == ENET_SOCKET_NULL || enet_socket_set_option(mLanSock, ENET_SOCKOPT_REUSEADDR, 1) < 0 || enet_socket_bind(mLanSock, &lanInfoAddress) < 0)
    {
        if (mLanSock != ENET_SOCKET_NULL)
			enet_socket_destroy(mLanSock);
        mLanSock = ENET_SOCKET_NULL;
		return false;
    }
	enet_socket_set_option(mLanSock, ENET_SOCKOPT_NONBLOCK, 1);

	return true;
}

void TcpServer::ResetItems()
{
	mServerEntities.resize(0);
}

void TcpServer::Update(float elapsedTime)
{
	if (mServerHost == NULL)
		return;

	// reply the server socket.
	CheckServerSockets();
	
    ENetEvent event;
    bool serviced = false;
	DWORD timeout = 0;

	// handle the net events.
    while(!serviced)
    {
        if (enet_host_check_events(mServerHost, &event) <= 0)
        {
            if (enet_host_service(mServerHost, &event, timeout) <= 0) 
				break;
            serviced = true;
		}

		// do some thing with the event type.
		switch (event.type)
		{
		case ENET_EVENT_TYPE_CONNECT:
			{
				Client& client = AddClient();
				client.type = ST_TCPIP;
				client.peer = event.peer;
				client.peer->data = &client;
				
				// fetch the host name.
                char hn[1024];
                client.hostname = (enet_address_get_host_ip(&client.peer->address, hn, sizeof(hn))==0) ? hn : "unknown";

				// here comes a client.
                int reason = ClientConnect(client.num, client.peer->address.host);
                if (reason)
					DisconnectClient(client.num, reason);
			}
			break;
		case ENET_EVENT_TYPE_RECEIVE:
			{
				Client* client = (Client*)event.peer->data;
				if (client != NULL)
					ReceivePacket(event.packet, client->num, event.channelID);

                if (event.packet->referenceCount == 0) 
					enet_packet_destroy(event.packet);
			}
			break;
		case ENET_EVENT_TYPE_DISCONNECT:
			{
                Client* client = (Client*)event.peer->data;
                if (client == NULL)
					break;

				ClientDisconnect(client->num);

				client->type = ST_EMPTY;
				client->peer->data = NULL;

				DeleteClientInfo((ClientInfo*)client->info);
				client->info = NULL;
			}
			break;
		default:
			break;
		}
	}

	// send the output packets.
	if (SendPackets())
		enet_host_flush(mServerHost);
}

void TcpServer::CheckServerSockets()
{
	static ENetSocketSet sockset;
	ENET_SOCKETSET_EMPTY(sockset);

	ENET_SOCKETSET_ADD(sockset, mPongSock);
	ENET_SOCKETSET_ADD(sockset, mLanSock);

	ENetSocket maxsock = Max(mPongSock, mLanSock);
	
	if (enet_socketset_select(maxsock, &sockset, NULL, 0) <= 0)
		return;

	ENetBuffer buf;
	BYTE pong[MAXTRANS];
	loopi(2)
	{
		ENetSocket sock = i ? mLanSock : mPongSock;
		if (sock == ENET_SOCKET_NULL || !ENET_SOCKETSET_CHECK(sockset, sock)) 
			continue;

		buf.data = pong;
		buf.dataLength = sizeof(pong);
		int len = enet_socket_receive(sock, &pongaddr, &buf, 1);
		if (len < 0)  // something error.
			return;

		// send the server info.
		ServerInfoCmd cmd;
		cmd.NumClients = 0;

		ServerInfoReply(&cmd, sizeof(ServerInfoCmd));
	}
}

void TcpServer::ServerInfoReply(void* buff, size_t len)
{
    ENetBuffer buf;
    buf.data = buff;
    buf.dataLength = len;
    enet_socket_send(mPongSock, &pongaddr, &buf, 1);
}

bool TcpServer::SendPackets()
{
	return true;
}

void TcpServer::SendMessage(int n, const Command* cmd, size_t len)
{
	SendPacket(n, CH_MESSAGE, cmd, len);
}

void TcpServer::SendMessageToAll(const Command* cmd, size_t len, int exclude)
{
	SendPacket(-1, CH_MESSAGE, cmd, len, exclude);
}

void TcpServer::SendPacket(int n, int channel, const Command* cmd, size_t len, int exclude, DWORD flag)
{
	ENet::ENetPacket* packet = enet_packet_create(cmd, len, flag);
	if (packet == NULL)
		return;
	SendPacket(n, channel, packet, exclude);
}

void TcpServer::SendPacket(int n, int channel, ENetPacket *packet, int exclude)
{
	// this is a brodcast packet.
	if (n < 0)
	{
		loopv(mClients) if (i != exclude) 
			SendPacket(i, channel, packet);
		return;
	}

	Client& client = mClients[n];
	switch (client.type)
	{
	case ST_TCPIP:
		enet_peer_send(client.peer, channel, packet);
		break;
	case ST_LOCAL:
		// send data directly to local client.
		TcpClient* tcpClient = NetworkManager::Instance().getClient();
		if (tcpClient != NULL)
			tcpClient->ReceivePacket(channel, packet);
		break;
	}
}

void TcpServer::ClientPosition(int sender, const Command* cmd, size_t len)
{
	if (sender >= static_cast<int>(mClients.size()))
		return;

	Client& client = mClients[sender];
	if (client.type == ST_EMPTY || client.info == NULL)
		return;

	ClientInfo* info = (ClientInfo*)client.info;
	switch (cmd->Type)
	{
	case CMD_UPDATE_POS:
		{
			const UpdatePositionCmd* cmdInfo = (const UpdatePositionCmd*)cmd;
			info->PosX = cmdInfo->PosX;
			info->PosY = cmdInfo->PosY;
			info->PosZ = cmdInfo->PosZ;
			info->RotX = cmdInfo->RotX;
			info->RotY = cmdInfo->RotY;
		}
		break;
	}
}

void TcpServer::ClientMessage(int sender, const Command* cmd, size_t len)
{
	if (sender >= static_cast<int>(mClients.size()))
		return;

	Client& client = mClients[sender];
	if (client.type == ST_EMPTY || client.info == NULL)
		return;

	ClientInfo* info = (ClientInfo*)client.info;
	switch (cmd->Type)
	{
	case CMD_CONNECT:
		{
			info->accept = true;

			WelcomeInfoCmd retCmd;
			SendMessage(sender, &retCmd, sizeof(retCmd));
		}
		break;
	}
}

void TcpServer::ReceivePacket(ENetPacket *packet, int sender, int channel)
{
	// parse the packet.
	switch (channel)
	{
	case CH_POSITION:
		ClientPosition(sender, (const Command*)packet->data, packet->dataLength);
		break;
	case CH_MESSAGE:
		ClientMessage(sender, (const Command*)packet->data, packet->dataLength);
		break;
	case CH_FILE:
		break;
	}
}

static bool IsEmptyClient(const TcpServer::Client& client) { return client.type == ST_EMPTY; }

TcpServer::Client& TcpServer::AddClient()
{
	// check is there a empty slot for incoming client.
	ClientList::iterator it = std::find_if(mClients.begin(), mClients.end(), IsEmptyClient);
    if (it != mClients.end())
    {
        it->info = NewClientInfo();
        return *it;
    }

	// create a new client.
	mClients.push_back(Client());
	Client& client = mClients.back();
	client.num = mClients.size();
	client.info = NewClientInfo();
    return client;
}

TcpServer::ClientInfo* TcpServer::NewClientInfo()
{
	ClientInfo* ret = new ClientInfo();
	ret->accept = false;
	return ret;
}

void TcpServer::DeleteClientInfo(TcpServer::ClientInfo* info)
{
	delete info;
}

int TcpServer::ClientConnect(int n, DWORD ip)
{
	assert (n < static_cast<int>(mClients.size()));

	Client& client = mClients[n];
	assert (client.type != ST_EMPTY);
	assert (client.info != NULL);

	// enqueue the connection.
	mConnects.push_back((ClientInfo*)client.info);
	return DISC_NONE;
}

void TcpServer::ClientDisconnect(int n)
{
	assert (n < static_cast<int>(mClients.size()));

	ClientInfo* info = (ClientInfo*)mClients[n].info;
	ClientInfoList::iterator it = std::find(mConnects.begin(), mConnects.end(), info);
	if (it != mConnects.end())
		mConnects.erase(it);
}

void TcpServer::DisconnectClient(int n, int reason)
{
	if (n >= static_cast<int>(mClients.size()))
		return;

	Client& client = mClients[n];
	if (client.type != ST_TCPIP)
		return;

    enet_peer_disconnect(client.peer, reason);

    ClientDisconnect(n);

    client.type = ST_EMPTY;
    client.peer->data = NULL;

    DeleteClientInfo((ClientInfo*)client.info);
    client.info = NULL;
}
